package com.ifonly.activerecord.callback;

import com.ifonly.activerecord.Db;
import com.ifonly.activerecord.annotation.Tx;
import com.ifonly.activerecord.exceptions.ActiveRecordException;
import com.ifonly.aop.Callback;
import com.ifonly.cache.CacheKeyHandler;
import com.ifonly.cache.annotation.Cacheable;
import com.ifonly.cache.annotation.Cachevict;
import com.ifonly.cache.interceptor.CacheInterceptor;
import com.ifonly.cache.interceptor.CacheableInterceptor;
import com.ifonly.cache.interceptor.CachevictInterceptor;
import com.ifonly.cache.invoke.CacheableAnnotation;
import com.ifonly.cache.invoke.CachevictAnnotation;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * Cglib 动态代理方法回调
 *
 *  用于进行 缓存, 事务 的处理
 *
 * @author ifonly
 * @version 1.0 2016-01-13 14:22
 * @since JDK 1.6
 */
public class ModelCallback extends Callback {
    private Db db = Db.getInstance();


    /**
     * 首先进行事务拦截, 在 doIntercept 进行其他处理
     *
     * @param o AOP对象
     * @param method 原始方法
     * @param objects 方法参数
     * @param methodProxy 代理方法
     * @return 方法执行结果
     * @throws Throwable Throwable对象
     */
    @Override
    public Object intercept(Object o, Method method,
                            Object[] objects,
                            MethodProxy methodProxy) throws Throwable {
        Tx tx = method.getAnnotation(Tx.class);
        if (tx != null) {
            Connection conn = db.getThreadLocalConnection();

            if (conn != null) {
                if (conn.getTransactionIsolation() < tx.level().value) {
                    conn.setTransactionIsolation(tx.level().value);
                }
                try {
                    return doIntercept(o, method, objects, methodProxy);
                } catch (Exception e) {
                    throw new ActiveRecordException(e);
                }
            } else {
                Object result;
                Boolean autoCommit = null;
                try {
                    conn = db.getConnection();
                    db.setThreadLocalConnection(conn);

                    autoCommit = conn.getAutoCommit();
                    conn.setAutoCommit(false);
                    if (conn.getTransactionIsolation() < db.getTransactionIsolation()) {
                        conn.setTransactionIsolation(db.getTransactionIsolation());
                    }
                    result = doIntercept(o, method, objects, methodProxy);
                    conn.commit();
                    conn.setAutoCommit(autoCommit);
                    return result;
                } catch (Throwable throwable) {
                    try {
                        if (conn != null)
                            conn.rollback();
                    } catch (SQLException e1) {
                        e1.printStackTrace();
                    }
                    throw new RuntimeException(throwable);
                } finally {
                    if (conn != null) {
                        try {
                            if (autoCommit != null) {
                                conn.setAutoCommit(autoCommit);
                            }
                        } catch (SQLException e) {
                            // ignore
                        }
                    }
                    db.closeAndRemoveThreadLocalConnection();
                }
            }
        } else {
            return doIntercept(o, method, objects, methodProxy);
        }
    }


    private Object doIntercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        CacheInterceptor[] interceptors = parseCacheInterceptor(method);
        ModelCacheInvocation inv = null;
        if (interceptors.length > 0) {
            inv = new ModelCacheInvocation(o, method, objects);
            inv.setInterceptors(interceptors);
            inv.setMethodProxy(methodProxy);
            inv.invoke();
        }

        if (inv != null && (inv.getResult() != null || inv.isMethodInvoked())) {
            return inv.getResult();
        }

        return super.intercept(o, method, objects, methodProxy);
    }

    private CacheInterceptor[] parseCacheInterceptor(Method method) {
        List<CacheInterceptor> interceptors = new ArrayList<CacheInterceptor>();

        Cacheable cacheable = method.getAnnotation(Cacheable.class);
        if (cacheable != null) {
            String[] names = cacheable.name();
            String key = cacheable.key();
            Class<? extends CacheKeyHandler>[] cacheKeyHandlers = cacheable.cacheKeyHandler();
            Class<? extends CacheKeyHandler> cacheKeyHandler = null;
            if (cacheKeyHandlers != null && cacheKeyHandlers.length > 0) {
                cacheKeyHandler = cacheKeyHandlers[0];
            }

            for (String name : names) {
                CacheableAnnotation cacheableAnnotation = new CacheableAnnotation(name, key, cacheKeyHandler);
                CacheableInterceptor interceptor = new CacheableInterceptor(cacheableAnnotation);
                interceptors.add(interceptor);
            }
        }

        Cachevict cachevict = method.getAnnotation(Cachevict.class);
        if (cachevict != null) {
            String[] names = cachevict.name();
            String key = cachevict.key();
            boolean all = cachevict.all();
            Class<? extends CacheKeyHandler>[] cacheKeyHandlers = cachevict.cacheKeyHandler();
            Class<? extends CacheKeyHandler> cacheKeyHandler = null;
            if (cacheKeyHandlers != null && cacheKeyHandlers.length > 0) {
                cacheKeyHandler = cacheKeyHandlers[0];
            }

            for (String name : names) {
                CachevictAnnotation cachevictAnnotation = new CachevictAnnotation(name, key, cacheKeyHandler);
                cachevictAnnotation.setAll(all);
                CachevictInterceptor interceptor = new CachevictInterceptor(cachevictAnnotation);
                interceptors.add(interceptor);
            }
        }

        return interceptors.toArray(new CacheInterceptor[interceptors.size()]);
    }

}
